#include "Log.hpp"
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <errno.h>
#include <string.h> //memset
#include <netinet/in.h> //struct sockaddr_in
#include <arpa/inet.h>
#include <thread>


uint16_t server_port;
std::string server_ip;
int socket_fd;

void receive_message()//收消息
{
    //ps: 循环内外声明变量会对效率有影响吗?
    //  1.对于基本类型，循环内外声明变量在效率上通常没有本质区别，因为编译器会优化。
    //  2.对于非基本类型，建议在循环外部声明，以避免重复构造和析构带来的开销。
    //  3.从代码风格上讲，尽量缩小变量的作用域，避免不必要的变量暴露在外，以提高代码的可维护性和减少错误。
    while(true)
    {
        char recv_buffer[1024];

        struct sockaddr_in tmp;//一个客户端可以访问多个服务端, 但我们这个的例子里只是1对1, 所以输出回来socket的还是我们命令行传过去那个
        socklen_t tmp_len = sizeof(tmp);                                                 
        ssize_t recv_byte = recvfrom(socket_fd, &recv_buffer, sizeof(recv_buffer) - 1, 0, (struct sockaddr*)&tmp, &tmp_len);
        if(recv_byte > 0)
        {
            recv_buffer[recv_byte] = '\0';
            std::cout << recv_buffer << std::endl;
        }

    }
}

void send_message()//发消息
{
    //2.bind
    //client不需要显示bind, 因为client会在首次发送消息时自动bind, 1.OS知道IP, 2.OS会给client自动分配端口号
    //  1.server需要手动传入端口号, 且端口号需要是知名且不可改变的
    //  2.client不需要手动传, 是因为本地OS里的client会非常多. 你手动传如果传冲突了, 就无法通信了, 所以OS会自动帮你分配不冲突的端口号
    struct sockaddr_in server_socket;
    memset(&server_socket, 0, sizeof(server_socket));//指定空间清零
    server_socket.sin_family = AF_INET;
    server_socket.sin_port = htons(server_port);//主机字节序 -> 网络字节序
    server_socket.sin_addr.s_addr = inet_addr(server_ip.c_str());//1.字符串 -> 32位, 2.主~ -> 网~

    std::string send_buffer;
    while(true)
    {
        std::cout << "please enter:> ";
        std::getline(std::cin, send_buffer);
        ssize_t send_byte = sendto(socket_fd, send_buffer.c_str(), send_buffer.size(), 0, (const sockaddr*)&server_socket, sizeof(server_socket));//都是输入型参数
    }
}



//主线程只负责创建socket, 收发消息交给从线程
int main(int argc, char* argv[])
{
    //./client_UDP [server_port] [server_ip]  
    if(argc != 3)
    {
        std::cout << "usage: " << argv[0] << " [server_port] [server_ip]" << std::endl;
        return 1;
    }

    server_port = static_cast<uint16_t>(std::stoi(argv[1]));
    server_ip = argv[2];


    //1.创建socket
    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);//前两个参数组合代表UDP协议通信
    if(socket_fd < 0)
    {
        LOG(wzx::log_level::FATAL) << "socket construct failed!!!";
        exit(errno);
    }
    LOG(wzx::log_level::INFO) << "socket construct successfully, _socket_fd: " << socket_fd;



    std::thread recv_thread(receive_message);
    std::thread send_thread(send_message);
    

    recv_thread.join();
    send_thread.join();


    return 0;
}